use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;

// TPM Basic Configuration
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TpmBaseConfig {
    pub tcti_config: String,              // TCTI configuration string
    pub ak_handle: Option<u32>,           // AK handle
    pub ak_nv_index: Option<u32>,         // AK NV index
    pub pcr_selections: Option<Vec<u32>>, // PCR selection list
    pub quote_hash_algo: Option<String>,  // Quote digest algorithm, e.g. "sha256"
}

// TPM Plugin Configuration
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TpmConfig {
    #[serde(flatten)]
    pub tpm_base: TpmBaseConfig,          // Contains basic TPM configuration
    pub event_log_path: String,           // Event log path
}

// IMA Configuration
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ImaConfig {
    #[serde(flatten)]
    pub tpm_base: TpmBaseConfig,          // Contains basic TPM configuration
    pub log_path: String,                 // IMA log path
    pub template_name: Option<String>,    // IMA template name
    pub ascii_runtime_measurements: bool,  // Whether to use ASCII format for runtime measurements
}

// DIM Configuration
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DimConfig {
    #[serde(flatten)]
    pub tpm_base: TpmBaseConfig,          // Contains basic TPM configuration
    pub dim_mode: String,                 // DIM mode
    pub dim_path: Option<String>,         // DIM path
    pub cache_dir: Option<String>,        // Cache directory
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "attester_type", rename_all = "lowercase")]
pub enum PluginParams {
    Tpm(TpmConfig),
    Ima(ImaConfig),
    Dim(DimConfig),
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PluginConfig {
    pub name: String,
    pub path: String,
    pub enabled: bool,
    pub params: Option<PluginParams>,
}

// Initial Random Delay Configuration
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InitialDelayConfig {
    pub min_seconds: u64,  // Minimum delay in seconds
    pub max_seconds: u64,  // Maximum delay in seconds
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SchedulerConfig {
    pub name: String,
    pub enabled: bool,
    pub cron_expression: String, // For example, "0 */6 * * *" to run every 6 hours
    pub initial_delay: Option<InitialDelayConfig>, // Optional startup delay configuration
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TlsConfig {
    pub enabled: bool,
    pub cert_path: String,
    pub key_path: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ServerConfig {
    pub server_url: String,
    pub timeout_seconds: u64,
    pub tls: Option<TlsConfig>,  // Optional TLS configuration
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AgentConfig {
    pub listen_address: String,
    pub listen_port: u16,
    pub uuid: Option<String>,  // Optional UUID to uniquely identify the agent
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LoggingConfig {
    pub level: String,
    pub file: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
    pub agent: AgentConfig,
    pub server: ServerConfig,
    pub plugins: Vec<PluginConfig>,
    pub schedulers: Vec<SchedulerConfig>,
    pub logging: LoggingConfig,
}

/// Configuration manager that handles loading and accessing configuration settings
///
/// The configuration file is loaded using the following priority order:
/// 1. Command line specified path (if provided and the file exists)
/// 2. Current working directory: ./agent_config.yaml
/// 3. System-wide configuration: /etc/attestation_agent/agent_config.yaml
///
/// If no configuration file is found in any of these locations, an error is returned.
#[derive(Clone)]
pub struct ConfigManager {
    config: Arc<Config>,
    config_path: String, // Records the actual configuration file path used
}

impl ConfigManager {
    pub async fn new(config_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let (actual_path, config) = Self::find_and_load_config(config_path).await?;
        
        Ok(Self {
            config: Arc::new(config),
            config_path: actual_path,
        })
    }
    
    async fn find_and_load_config(cli_path: &str) -> Result<(String, Config), Box<dyn std::error::Error>> {
        if !cli_path.is_empty() {
            let path = PathBuf::from(cli_path);
            if path.exists() {
                return Ok((cli_path.to_string(), Self::load_config(&path).await?));
            }
        }
        
        let current_dir_config = PathBuf::from("agent_config.yaml");
        if current_dir_config.exists() {
            return Ok((current_dir_config.to_string_lossy().to_string(), 
                      Self::load_config(&current_dir_config).await?));
        }
        
        let etc_config = PathBuf::from("/etc/attestation_agent/agent_config.yaml");
        if etc_config.exists() {
            return Ok((etc_config.to_string_lossy().to_string(), 
                      Self::load_config(&etc_config).await?));
        }

        Err("Could not find configuration file. Tried command line path, current directory, and /etc directory.".into())
    }

    async fn load_config(path: &Path) -> Result<Config, Box<dyn std::error::Error>> {
        let config_str = fs::read_to_string(path)?;
        let config: Config = serde_yaml::from_str(&config_str)?;
        Ok(config)
    }

    pub async fn get_config(&self) -> Config {
        (*self.config).clone()
    }

    pub fn get_config_path(&self) -> &str {
        &self.config_path
    }
}